﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters;

namespace peonwar
{
    public enum RequestType
    {
        None = 0,
        ConsoleMessage = 1,
        ConsoleCommand = 2,
        Gc_SendProfilInfos = 3,
        Gc_SwapTeams = 4,
        Gc_Ready = 5,
        Gc_ChangeMap = 6,
        Gc_ChangeTime = 7,
        Gc_ChangeBase = 8,
        Gc_ChangeMoney = 9,
        Gc_StartGame = 10,
        Gc_LobbyChatMessage = 11,
        Gc_ReturnToLobby = 12,
        Gc_ReturnToLobbyAndSendProfil = 13,
        Game_ReturnToLobbyAgreement = 14,
        Game_SpawnUnit = 15,
        Game_UpdateUnit = 16,
        Game_Chat = 17,
        Game_BaseUpgrade = 18,
        Game_Power = 19,
        Game_GetDrain = 20,
        Game_KillUnit = 21,
        Game_Synchronize = 22,
        Game_ResearchTen = 23,
        Game_ResearchEleven = 24,
        Game_ProductionLevel = 25,
        Version_Compatibility = 26,
    }

    static class CommandProcessor
    {
        public static Queue<Tuple<RequestType, object>> PendingCommands;
        private static Thread Processing;
        private static Game game;
        private static Network Net;

        #region Start
        public static void Start(Game g, Network net)
        {
            game = g;
            Net = net;

            PendingCommands = new Queue<Tuple<RequestType, object>>();

            Processing = new Thread(new ThreadStart(ProcessCommand));
            Processing.IsBackground = true;
            Processing.Start();

            // Mort du Thread server/client
        }
        #endregion

        public static void ProcessCommand()
        {
            while (Net.AllowReceive)
            {
                #region Reception des procedures
                try
                {
                    Tuple<RequestType, object> reception = Net.Receive();
                    if (game.CurrentSession == Session.OnlineHOST)
                        Net.SyncDelay = Net.SyncDelay + 30;
                    if (reception != null)
                        CommandProcessor.PendingCommands.Enqueue(reception);
                }
                catch (Exception e)
                {
                    // Si on est autoriser a recevoir (allowreceive = true)
                    // et qu'on a pas reussi (on est dans le catch) c'est que la
                    // connection distante a ete fermer par autre que nous -> on deco
                    if (Net.AllowReceive)
                        Net.Disconnect(true, GameData.GameString[218], e.Message);
                    // Sinon cest que l'erreur du bloc catch est normal et qu'on
                    // a close nous meme la connection -> pas besoin de deco, c'est deja 
                    // en train de se faire.
                }
                #endregion

                #region Traitement des procedures
                while (PendingCommands.Count > 0)
                {
                    Tuple<RequestType, object> Request;
                    Request = PendingCommands.Dequeue();
                    switch (Request.Item1)
                    {
                        case RequestType.None: break;
                        case RequestType.Game_Synchronize:
                            Game_Synchronize((SynchroInfos)Request.Item2); break;
                        case RequestType.ConsoleMessage:
                            ConsoleMessage((string)Request.Item2); break;
                        case RequestType.ConsoleCommand:
                            ConsoleCommand((string)Request.Item2); break;
                        case RequestType.Gc_SendProfilInfos:
                            Gc_SendProfilInfos((ProfilInfos)Request.Item2); break;
                        case RequestType.Gc_SwapTeams:
                            Gc_SwapTeams((bool)Request.Item2); break;
                        case RequestType.Gc_Ready:
                            Gc_Ready((Boolean)Request.Item2); break;
                        case RequestType.Gc_ChangeMap:
                            Gc_ChangeMap((Map)Request.Item2); break;
                        case RequestType.Gc_ChangeMoney:
                            Gc_ChangeMoney((int)Request.Item2); break;
                        case RequestType.Gc_ChangeTime:
                            Gc_ChangeTime((int)Request.Item2); break;
                        case RequestType.Gc_ChangeBase:
                            Gc_ChangeBase((int)Request.Item2); break;
                        case RequestType.Gc_StartGame:
                            Gc_StartGame(); break;
                        case RequestType.Gc_LobbyChatMessage:
                            Gc_LobbyChatMessage((string)Request.Item2); break;
                        case RequestType.Gc_ReturnToLobby:
                            Gc_ReturnToLobby(); break;
                        case RequestType.Gc_ReturnToLobbyAndSendProfil:
                            Gc_ReturnToLobbyAndSendProfil((ProfilInfos)Request.Item2); break;
                        case RequestType.Game_ReturnToLobbyAgreement:
                            Game_ReturnToLobbyAgreement((bool)Request.Item2); break;
                        case RequestType.Game_SpawnUnit:
                            Game_SpawnUnit((UnitInfos)Request.Item2); break;
                        case RequestType.Game_UpdateUnit:
                            Game_UpdateUnit((UnitTransfert)Request.Item2); break;
                        case RequestType.Game_Chat:
                            Game_Chat((string)Request.Item2); break;
                        case RequestType.Game_BaseUpgrade:
                            Game_BaseUpgrade((BaseInfos)Request.Item2); break;
                        case RequestType.Game_Power:
                            Game_Power((Tuple<Power, int>)Request.Item2); break;
                        case RequestType.Game_GetDrain:
                            Game_GetDrain((double)Request.Item2); break;
                        case RequestType.Game_KillUnit:
                            Game_KillUnit(); break;
                        case RequestType.Game_ResearchTen:
                            Game_ResearchTen(); break;
                        case RequestType.Game_ResearchEleven:
                            Game_ResearchEleven(); break;
                        case RequestType.Game_ProductionLevel:
                            Game_ProductionLevel(); break;
                        case RequestType.Version_Compatibility:
                            Version_Compatibility((Version)Request.Item2); break;

                        default: return;
                    }
                }
                #endregion
            }
        }

        #region Commands

        #region Commandes Techniques
        public static void ConsoleMessage(string text)
        {
            DevConsole.AddOutput(text);
        }
        public static void ConsoleCommand(string text)
        {
            DevConsole.AddOutput(DevConsole.GetAction(text, game));
        }
        public static void Version_Compatibility(Version version)
        {
            if (game.version != version)
            {
                game.Network_Client.Disconnect(true, GameData.GameString[310], "");
            }
        }
        #endregion

        #region Commandes Lobby
        public static void Gc_SendProfilInfos(ProfilInfos infos)
        {
            game.Network_OpponentInfos = infos;
        }
        public static void Gc_ReturnToLobbyAndSendProfil(ProfilInfos infos)
        {
            Gc_ReturnToLobby();
            Gc_SendProfilInfos(infos);
            game.Network_Client.Send(RequestType.Gc_SendProfilInfos, new ProfilInfos(SettingManager.CurrentProfil));
        }
        public static void Gc_SwapTeams(bool opponentteam)
        {
            game.NewGame_Online.PlayerIsBlue = !opponentteam;

            if (game.NewGame_Online.PlayerIsBlue)
                game.Lobby_Team.ChangeText(GameData.GameString[205] + " - " + GameData.GameString[206]);
            else
                game.Lobby_Team.ChangeText(GameData.GameString[206] + " - " + GameData.GameString[205]);
        }
        public static void Gc_Ready(bool ready)
        {
            game.opponent_ready = ready;
        }
        public static void Gc_ChangeMap(Map newmap)
        {
            game.NewGame_Online.Map = newmap;
            game.Lobby_Map.ChangeText(game.NewGame_Online.Map.ToString());
        }
        public static void Gc_ChangeMoney(int money)
        {
            game.NewGame_Online.startmoney = money;
            game.Lobby_StartMoney.ChangeText(game.NewGame_Online.startmoney + " $");
        }
        public static void Gc_ChangeTime(int time)
        {
            game.NewGame_Online.partietime = time;
            if (time != -1)
                game.Lobby_Time.ChangeText(Tools.ToMinute((int)time));
            else game.Lobby_Time.ChangeText(GameData.GameString[93]);
        }
        public static void Gc_ChangeBase(int health)
        {
            game.NewGame_Online.StartBaseHealth = health;
            game.Lobby_Health.ChangeText(game.NewGame_Online.StartBaseHealth.ToString());
        }
        public static void Gc_StartGame()
        {
            if (game.NewGame_Online.PlayerIsBlue)
            {
                game.NewGame_Online.blue_perso = SettingManager.CurrentProfil.Character;
                game.NewGame_Online.red_perso = game.Network_OpponentInfos.Character;
            }
            else
            {
                game.NewGame_Online.red_perso = SettingManager.CurrentProfil.Character;
                game.NewGame_Online.blue_perso = game.Network_OpponentInfos.Character;
            }
            DevConsole.CheatsEnabled = false;
            DevConsole.DevMode = false;
            DevConsole.PanicMode = false;
            game.InitializeGame(game.NewGame_Online);
        }
        public static void Gc_LobbyChatMessage(string message)
        {
            string pseudo = game.Network_OpponentInfos.Name;
            game.Lobby_chat.EnterChat(pseudo + ": " + message, game.Lobby_chat.PlayerColor);
        }
        public static void Gc_ReturnToLobby()
        {
            game.IWantToGoLobby = false;

            MediaPlayer.Play(GameSound.MenuMusic);
            game.EndGame();

            game.player_ready = false;
            game.opponent_ready = false;
            Game.gamestate = Gamestates.Lobby;
        }
        #endregion

        #region Commandes Jeu
        public static void Game_SpawnUnit(UnitInfos infos)
        {
            Unit u = UnitInfos.GetDistantUnit(infos, Game.p2_opponent);
            User.AdvertList.Add(new Advert(new Vector2(u.Position.X - 5, u.Position.Y - 45), GameData.Font_Pause, "+", 1.3f, new Vector2(0, -1), 1.8f, u.TeamColor, true, false));
            User.AdvertList.Add(new Advert(new Vector2(u.Position.X - 13, u.Position.Y - 50), GameData.Font_Menu, "+", 1.5f, new Vector2(0, -1), 1.0f, u.TeamColor, true, false));
            User.AdvertList.Add(new Advert(new Vector2(u.Position.X + 3, u.Position.Y - 45), GameData.Font_Pause, "+", 1.2f, new Vector2(0, -1), 1.5f, u.TeamColor, true, false));
            User.AdvertList.Add(new Advert(new Vector2(u.Position.X, u.Position.Y - 40), GameData.Font_Menu, "+", 1f, new Vector2(0, -1), 1.6f, u.TeamColor, true, false));
            Game.p2_opponent.SpawnUnit(u);
        }
        public static void Game_UpdateUnit(UnitTransfert transf)
        {
            for (int i = 0; i < Game.p2_opponent.PeonList.Count; i++)
            {
                if (transf.Identifier == Game.p2_opponent.PeonList[i].Identifier)
                {
                    Game.p2_opponent.PeonList[i].ActualizeUnit(transf, Game.p1_player.PeonList);
                    break;
                }
            }
        }
        public static void Game_Chat(string message)
        {
            if (game.Game_chat.PlayerColor == Color.Blue)
                game.Game_chat.EnterChat(message, Color.Red);
            else game.Game_chat.EnterChat(message, Color.Blue);
        }
        public static void Game_BaseUpgrade(BaseInfos infos)
        {
            Game.p2_opponent.MaxBaseHealth = infos.MaxBaseHealth;
            Game.p2_opponent.BaseHealth = infos.BaseHealth;
            Game.p2_opponent.BaseHealthLevel = infos.BaseHealthLevel;
        }
        public static void Game_Power(Tuple<Power, int> power)
        {
            if (power.Item1 == Power.EnergyDrainer)
            {
                if (game.CurrentSession == Session.OnlineHOST)
                    game.Network_HostServer.Send(RequestType.Game_GetDrain, Game.p1_player.Cooldown);
                else
                    game.Network_Client.Send(RequestType.Game_GetDrain, Game.p1_player.Cooldown);
                Game.p1_player.Cooldown = 0;
            }
            else
                Game.p2_opponent.LaunchPower(power.Item1, Game.p1_player, power.Item2);
        }
        public static void Game_GetDrain(double cooldown)
        {
            if (Game.p1_player.Cooldown > 0)
                Game.p1_player.Cooldown += cooldown;
            else
                Game.p1_player.Cooldown += 10;

            if (Game.p1_player.Cooldown > 100)
                Game.p1_player.Cooldown = 100;
        }
        public static void Game_KillUnit()
        {
            Game.p2_opponent.Kill(Game.p1_player);
        }
        public static void Game_ReturnToLobbyAgreement(bool agree)
        {
            game.OpponentWantToGoLobby = agree;
        }
        public static void Game_Synchronize(SynchroInfos infos)
        {
            if (infos == null)
                return;
            for (int i = 0; i < infos.blue.Length; i++)
            {
                for (int j = 0; j < Game.blue.PeonList.Count; j++)
                {
                    if (infos.blue[i].Identifier == Game.blue.PeonList[j].Identifier)
                        Game.blue.PeonList[j].ActualizeUnit(infos.blue[i], Game.red.PeonList);
                }
            }
            for (int i = 0; i < infos.red.Length; i++)
            {
                for (int j = 0; j < Game.red.PeonList.Count; j++)
                {
                    if (infos.red[i].Identifier == Game.red.PeonList[j].Identifier)
                        Game.red.PeonList[j].ActualizeUnit(infos.red[i], Game.blue.PeonList);
                }
            }

            Game.blue.MaxBaseHealth = infos.blueBase.MaxBaseHealth;
            Game.blue.BaseHealth = infos.blueBase.BaseHealth;
            Game.blue.BaseHealthLevel = infos.blueBase.BaseHealthLevel;

            Game.red.MaxBaseHealth = infos.redBase.MaxBaseHealth;
            Game.red.BaseHealth = infos.redBase.BaseHealth;
            Game.red.BaseHealthLevel = infos.redBase.BaseHealthLevel;

            game.GameChrono.ElapsedTime = infos.GameElapsedTime;

            GameServer.SyncOperation++;
        }
        public static void Game_ResearchTen()
        {
            Game.p2_opponent.bombBonus = true;
        }
        public static void Game_ResearchEleven()
        {
            Game.p2_opponent.baseBonus = true;
        }
        public static void Game_ProductionLevel()
        {
            Game.p2_opponent.UpgradeProductionLevel();
        }
        #endregion

        #endregion

        #region Tools
        public static byte[] ObjectToByteArray(Object obj)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();

                bf.Serialize(ms, obj);
                ms.Close();
                ms.Dispose();
                Byte[] aux = ms.GetBuffer();
                return aux;
            }
        }

        public static object ByteArrayToObject(byte[] arrBytes)
        {
            using (MemoryStream memStream = new MemoryStream())
            {
                BinaryFormatter binForm = new BinaryFormatter();

                memStream.Write(arrBytes, 0, arrBytes.Length);
                memStream.Position = 0;
                Object obj = binForm.Deserialize(memStream);
                memStream.Close();
                memStream.Dispose();
                return obj;
            }
        }

        public static void ReadWholeArray(Stream stream, byte[] data)
        {
            int offset = 0;
            int remaining = data.Length;
            while (remaining > 0)
            {
                int read = stream.Read(data, offset, remaining);
                if (read <= 0)
                    throw new EndOfStreamException
                        (String.Format("End of stream reached with {0} bytes left to read", remaining));
                remaining -= read;
                offset += read;
            }
        }

        /// <summary>
        /// Reads data from a stream until the end is reached. The
        /// data is returned as a byte array. An IOException is
        /// thrown if any of the underlying IO calls fail.
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        /// <param name="initialLength">The initial buffer length</param>
        public static byte[] ReadFully(Stream stream, int initialLength)
        {
            // If we've been passed an unhelpful initial length, just
            // use 32K.
            if (initialLength < 1)
            {
                initialLength = 32768;
            }

            byte[] buffer = new byte[initialLength];
            int read = 0;

            int chunk;
            while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
            {
                read += chunk;

                // If we've reached the end of our buffer, check to see if there's
                // any more information
                if (read == buffer.Length)
                {
                    int nextByte = stream.ReadByte();

                    // End of stream? If so, we're done
                    if (nextByte == -1)
                    {
                        return buffer;
                    }

                    // Nope. Resize the buffer, put in the byte we've just
                    // read, and continue
                    byte[] newBuffer = new byte[buffer.Length * 2];
                    Array.Copy(buffer, newBuffer, buffer.Length);
                    newBuffer[read] = (byte)nextByte;
                    buffer = newBuffer;
                    read++;
                }
            }
            // Buffer is now too big. Shrink it.
            byte[] ret = new byte[read];
            Array.Copy(buffer, ret, read);
            return ret;
        }
        #endregion
    }
}
